/***************************************************************************
* Copyright  Faraday Technology Corp 2002-2003.  All rights reserved.      *
*--------------------------------------------------------------------------*
* Name:ssp.c                                                               *
* Description: SSP C library routine                                       *
* Author: Fred Chien                                                       *
****************************************************************************/

#include "ssp.h"
#include "dma.h"
#include "gpio.h"
#include "DrvUART010.h"


int TxFIFOLen = 0;
int RxFIFOLen = 0;

void fLib_I2S_Init(UINT32 base_address, UINT32 mode)
{
	UINT32 data;
	unsigned int master_slave;
	
#if defined(FTSSP_AS_MASTER)
	master_slave = SSP_OPM_MSST;
#elif defined(FTSSP_AS_SLAVE)
	master_slave = SSP_OPM_SLST;
#else
	#error "unknown master/slave"
#endif
    fLib_printf("Initializing I2S...\r\n");
	if( mode == W6631TS_I2S )
	{
	  	// FDIST = 1, 24 bits, padding 7 bits, word_length = 16 bits
	  	// SSP main clock input clock = source clock / 2
    	// outw(SSPCK_BASE,0x00);
		data = I2S_Format | 0x100;
		outw(base_address + SSP_CONTROL0,data);
		fLib_SetSSPOPMode(base_address, master_slave);
	  	fLib_SetSSPDataLen(base_address,15);
    	fLib_SetSSPpaddingDataLen(base_address,6);
	  	fLib_SetSSPClkdiv(base_address,3);
	}
	else if ( mode == UDA1345TS_I2S )
	{
	  	//UDA1345TS's distance between fram/sync and first data ==> FSDIST = 1
	  	// 16 bits, MSB first, SSP_DATAJUSTIFY set to 1 ==> pad data in the front of serial data
	  	// 16.9344MHz clock => 22.05K sample rate
    	//outw(SSPCK_BASE,0x00);  //dvided 2
		data = I2S_Format | 0x100 | SSP_DATAJUSTIFY;// frame sync must shift 1 bits when I2S mode
		outw(base_address + SSP_CONTROL0,data);
		fLib_SetSSPOPMode(base_address, master_slave);
	  	fLib_SetSSPDataLen(base_address,0xF);
	  	fLib_SetSSPClkdiv(base_address,0x3);
	}
  	else if ( mode == NORMAL_I2S )
  	{
  	    master_slave = SSP_OPM_MSMO;
    	// 16 bits, FDIST = 1, padding data in the back of serial data
    	// 16.9344MHz clock => 22.05K sample rate
		//    outw(SSPCK_BASE,0x00);  //dvided 2
		data = I2S_Format | 0x100;
		outw(base_address + SSP_CONTROL0,data);
		fLib_SetSSPOPMode(base_address, master_slave);		
	  	fLib_SetSSPDataLen(base_address,0xF);   
  	}
  	else if ( mode == FSA0AC108_I2S )
  	{
    	// 16 data bits, 16 padding data bits
    	// FDIST = 1, padding data in the back of serial data
    	// 12.288MHz clock
		data = I2S_Format | 0x100 | SSP_FSPO_LOW;
		outw(base_address + SSP_CONTROL0,data);
		fLib_SetSSPOPMode(base_address, master_slave);	/* ssp as master */
	  	fLib_SetSSPDataLen(base_address,0xF);
	  	fLib_SetSSPpaddingDataLen(base_address, 0x10);
	  	fLib_SetSSPClkdiv(base_address,0xB);    
  	}
  	else if ( mode == WM8510_I2S )
  	{
    	// 16 data bits, 16 padding data bits
    	// FDIST = 1, padding data in the back of serial data
    	// 12.288MHz clock
		data = I2S_Format | 0x100 | SSP_FSPO_LOW;
		outw(base_address + SSP_CONTROL0,data);
		fLib_SetSSPOPMode(base_address, master_slave);
	  	fLib_SetSSPDataLen(base_address,0xF);
	  	fLib_SetSSPpaddingDataLen(base_address, 0x10);
	  	fLib_SetSSPClkdiv(base_address,0x3);
  	}
}


void fLib_SSP_Init(UINT32 base_address)
{
    fLib_SetSSPFrame(base_address,SSP_Format);
    fLib_SetSSPDataLen(base_address,0xF);
    fLib_SetSSPClkdiv(base_address,0x3);
}


void fLib_SetSSPFrame(UINT32 base_address,UINT32 frame)
{
UINT32 data; 

	data = inw(base_address + SSP_CONTROL0); 
	data &= 0xFFF;
	data |= frame;
	outw(base_address + SSP_CONTROL0, data); 
}	


void fLib_SetSSPFramePolar(UINT32 base_address,UINT32 polar)
{
UINT32 data; 

	data = inw(base_address + SSP_CONTROL0); 
	data &= ~0x20;
	data |= polar;
  	outw(base_address + SSP_CONTROL0, data);
}	


void fLib_SetSSPOPMode(UINT32 base_address,UINT32 mode)
{
UINT32 data; 

	data = inw(base_address + SSP_CONTROL0); 
	data &= ~0xC;
	data |= mode;
  	outw(base_address + SSP_CONTROL0, data);
}	


void fLib_SetSSPDataLen(UINT32 base_address,UINT32 len)
{
UINT32 data; 

	data = inw(base_address + SSP_CONTROL1); 
	data &= ~ SSP_SDL;
	data |= len << 16;
  	outw(base_address + SSP_CONTROL1, data);
}  

// adding function by Peter Liao from Faraday WinCE BSP

void fLib_SetSSPpaddingDataLen(UINT32 base_address,UINT32 len)
{
	UINT32 data; 

	data = inw(base_address + SSP_CONTROL1);
	data &= ~ SSP_PDL;
	data |= len << 24;
  outw(base_address + SSP_CONTROL1, data);
}  
 
void fLib_SetSSPClkdiv(UINT32 base_address,UINT32 divider)
{
UINT32 data; 

	data = inw(base_address + SSP_CONTROL1); 
	data &= ~ SSP_CLKDIV;
	data |= divider;
  	outw(base_address + SSP_CONTROL1, data);
}  
  

void fLib_SSPClearTxFIFO(UINT32 base_address)
{
UINT32 data; 

	data = inw(base_address + SSP_CONTROL2); 
	data |= SSP_TXFCLR;
  	outw(base_address + SSP_CONTROL2, data);
} 


void fLib_SSPClearRxFIFO(UINT32 base_address)
{
  UINT32 data; 

	data = inw(base_address + SSP_CONTROL2); 
	data |= SSP_RXFCLR;
  outw(base_address + SSP_CONTROL2, data); 
} 


void fLib_WriteSSP(UINT32 base_address,UINT32 data)
{ 
 	outw(base_address + SSP_DATA, data);
} 


UINT32 fLib_ReadSSPStatus(UINT32 base_address)
{
UINT32 data; 

	data = inw(base_address + SSP_STATUS); 
   	return data;	
}

	
UINT32 fLib_ReadSSP(UINT32 base_address)
{
UINT32 data; 

	data = inw(base_address + SSP_DATA); 
 	return data;
} 

void fLib_SetSSPSCLKPO(UINT32 base_address,UINT32 spolarity)
{
UINT32 data; 

	data = inw(base_address + SSP_CONTROL0); 
	data &= ~0x2;
	data |= spolarity << 1;
  	outw(base_address + SSP_CONTROL0, data);	
}	


void fLib_SetSSPSCLKPH(UINT32 base_address,UINT32 sphase)
{
UINT32 data; 

	data = inw(base_address + SSP_CONTROL0); 
	data &= ~0x1;
	data |= sphase;
  	outw(base_address + SSP_CONTROL0, data);
}	


UINT32 fLib_ReadSSPIntStatus(UINT32 base_address)
{
UINT32 data; 

	data = inw(base_address + SSP_INT_STATUS); 
   	return data;	
}


void fLib_SetSSP_TXFIFO(UINT32 base_address,UINT32 threshold,UINT32 underrun)
{
UINT32 data; 

	data = inw(base_address + SSP_INT_CONTROL); 
  		
	if (threshold)
  		data |= SSP_TFIEN; 	
  	else
  		data &= ~SSP_TFIEN;
  		
  	if (underrun)
  		data |= SSP_TFURIEN; 	
  	else
  		data &= ~SSP_TFURIEN; 
  		
  	outw(base_address + SSP_INT_CONTROL, data); 			  		
}	

void fLib_SetSSP_RXFIFO(UINT32 base_address,UINT32 threshold,UINT32 underrun)
{
UINT32 data; 

	data = inw(base_address + SSP_INT_CONTROL); 
  		
	if (threshold)
  		data |= SSP_RFIEN; 	
  	else
  		data &= ~SSP_RFIEN;
  		
  	if (underrun)
  		data |= SSP_RFORIEN; 	
  	else
  		data &= ~SSP_RFORIEN; 
  		
  	outw(base_address + SSP_INT_CONTROL, data);
}	

void fLib_SetSSP_DMA(UINT32 base_address,UINT32 trans,UINT32 rec)
{
    UINT32 data; 

	data = inw(base_address + SSP_INT_CONTROL); 
  		
	if (trans)
  		data |= SSP_TXDMAEN;
  	else
  		data &= ~SSP_TXDMAEN;
  		
  	if (rec)
  		data |= SSP_RXDMAEN;
  	else
  		data &= ~SSP_RXDMAEN; 
  		
  	outw(base_address + SSP_INT_CONTROL, data); 		 			  		
}	


void fLib_SetSSP_FIFO_Threshold(UINT32 base_address,UINT32 trans_len,UINT32 rec_len)
{
UINT32 	ctrl;
	ctrl = inw(base_address + SSP_INT_CONTROL);	
	
	ctrl &= ~0x0000FF00;
	ctrl |= ((trans_len << 12) + (rec_len << 7));
	
	outw(base_address + SSP_INT_CONTROL, ctrl);
}	


void fLib_SetSSP_WarmReset(UINT32 base_address)
{
UINT32 	ctrl;
	
	outw(base_address + SSP_CONTROL2, SSP_ACWRST | SSP_TXDOE);
	do
	{
		ctrl = inw(base_address + SSP_CONTROL2);
	}while((ctrl & SSP_ACWRST) != 0);
}

void fLib_SetSSP_ColdReset(UINT32 base_address)
{
UINT32 	ctrl;
	
	outw(base_address + SSP_CONTROL2, SSP_ACCRST | SSP_TXDOE); //
	do
	{
		ctrl = inw(base_address + SSP_CONTROL2);
	}while((ctrl & SSP_ACCRST) != 0);
}


void fLib_SetSSP_Enable(UINT32 base_address,int enable)
{
UINT32 	ctrl;
	
	ctrl = inw(base_address + SSP_CONTROL2);
	if(enable)
		ctrl |= SSP_SSPEN + SSP_TXDOE;//<====need enable TXDOE???
	else
		ctrl &= ~(SSP_SSPEN + SSP_TXDOE);
	
	outw(base_address + SSP_CONTROL2, ctrl);
}

void fLib_SetSSP_IntMask(UINT32 base_address,int Mask)
{
UINT32 	ctrl;
	ctrl = inw(base_address + SSP_INT_CONTROL);
	ctrl &= ~0x3F;
	ctrl |= Mask;	
	outw(base_address + SSP_INT_CONTROL, ctrl);
}

UINT32 fLib_SSP_GetTxFIFOLen(UINT32 base_address)
{
	UINT32 len;
	len = (inw(base_address + SSP_INFO)&SSP_TXFIFO_DEPTH);	
	TxFIFOLen = (len >> 16) + 1;
	return TxFIFOLen;
}

UINT32 fLib_SSP_GetRxFIFOLen(UINT32 base_address)
{
	UINT32 len;
	len = (inw(base_address + SSP_INFO)&SSP_RXFIFO_DEPTH);	
	RxFIFOLen = (len >> 8) + 1;
	
	return RxFIFOLen;
}


UINT32 fLib_SSP_GetRxFIFOValidEntries(UINT32 base_addr)
{
	return ((fLib_ReadSSPStatus(base_addr) & 0x01f0) >> 4);
}


void fLib_AC97_SetSlotValidReg(UINT32 base_address,UINT32 SlotValid)
{
	outw(base_address + SSP_ACLINK_SLOT_VALID, SlotValid);	
}

void fLib_InitAC97(UINT32 base_address)
{
//	outw(SSPCK_BASE, 0xF00);

	fLib_SetSSPFrame(base_address,AC97_Format);

	fLib_SSP_GetTxFIFOLen(base_address);
	fLib_SSP_GetRxFIFOLen(base_address);
		  
	fLib_SetSSP_ColdReset(base_address);

	fLib_SetSSP_Enable(base_address,false);
	   
	fLib_SetSSP_IntMask(base_address,0);

	fLib_SetSSP_FIFO_Threshold(base_address,4,4);

}

void fLib_AC97_WriteData(UINT32 base_address,UINT32 *data, UINT32 Len)
{
UINT32 	tx_cnt;
	
	while(Len != 0)
	{
		tx_cnt = (inw(base_address + SSP_STATUS) & SSP_TFVE) >> 12;		
				
		for(; tx_cnt < TxFIFOLen; tx_cnt++, data++, Len--)
		{
			if(Len == 0)
				return ;
				
			outw(base_address + SSP_DATA, *data);
		}
	}
	return;
}


void fLib_AC97_ReadData(UINT32 base_address,UINT32 *data, UINT32 Len)
{
	UINT32 	rx_cnt;

	while(Len != 0)
	{
		rx_cnt = (inw(base_address + SSP_STATUS) & SSP_RFVE) >> 4;
		
		for(; rx_cnt > 0; rx_cnt--, data++, Len--)
		{
			if(Len == 0)
				return;
				
			*data = inw(base_address + SSP_DATA);
		}
	}	
} 
UINT32 fLib_ReadSSP32Bit(UINT32 base_address)
{
	UINT32 data; 

	data = inw(base_address + SSP_DATA); 
 	return data;
} 
UINT32 fLib_AC97_ReadOneWordData(UINT32 base_address)
{
	while(!(inw(base_address + SSP_STATUS) & SSP_RFVE))
		;
				
	return inw(base_address + SSP_DATA);
}


int fLib_AC97_ReadRegister(UINT32 base_address,UINT32 Reg_Index, UINT16 *data)
{
UINT32 TxBuf[3], RxBuf[3];
	
 	TxBuf[0] = 0xE000;
	TxBuf[1] = 0x80000+(Reg_Index<<12);
	TxBuf[2] = 0;

	fLib_SSPClearTxFIFO(base_address);
	fLib_SSPClearRxFIFO(base_address);
	
	fLib_AC97_SetSlotValidReg(base_address,0x7);
	
	fLib_AC97_WriteData(base_address,TxBuf, 3);
	
	fLib_SetSSP_Enable(base_address,true);
	
	while(1)
	{
		fLib_AC97_ReadData(base_address,RxBuf, 1);
		//check reg index is match
		if((RxBuf[0] & 0xE000) == 0xE000) 
		{
			fLib_AC97_ReadData(base_address,RxBuf, 1);
			if(((RxBuf[0] >> 12) & 0x7f) == Reg_Index)
			{
				fLib_AC97_ReadData(base_address,RxBuf, 1);
				*data = (RxBuf[0] >> 4) & 0x0000ffff;
			}
			fLib_SetSSP_Enable(base_address,false);
			break;
		}
	}
	return true;
}

int fLib_ReturnTxFIFO_Count(UINT32 base_address)
{
	int count = 0;
	
	count = (inw(base_address+SSP_STATUS)&SSP_TFVE)>>12;
	
	return count;
}

int fLib_ReturnRxFIFO_Count(UINT32 base_address)
{
	int count = 0;
	
	count = (inw(base_address+SSP_STATUS)&SSP_RFVE)>>4;
	
	return count;
}

int fLib_AC97_ReadRegisterEx(UINT32 base_address,UINT32 Reg_Index, UINT16 *data)
{
	UINT32 TxBuf[2], RxBuf[2];
	
	*data = 0xFFFF;
	
	TxBuf[0] = 0x80000+(Reg_Index<<12);			// Command address slot (slot1)
	TxBuf[1] = 0;									// Command Data port slot (slot2)

	fLib_SSPClearTxFIFO(base_address);
	fLib_SSPClearRxFIFO(base_address);
	
	fLib_AC97_SetSlotValidReg(base_address,0xE000);	// slot0, slot1, slot2
	
	//##########################################################################
	// when enable SSP controler, data will send immediately at the first frame,
	// but receive data will be at next frame
	// to make sure second frame will not send wrong data,
	// we must fill 2 set of command to prevent second data to be wrong
	//##########################################################################
	fLib_AC97_WriteData(base_address,TxBuf, 2);
	fLib_AC97_WriteData(base_address,TxBuf, 2);
	
	fLib_SetSSP_Enable(base_address,true);
	
	while(fLib_ReturnTxFIFO_Count(base_address)>0)
		;
		
	fLib_SetSSP_Enable(base_address,false);
		
	// data will be in the second frame, so pump 2 fifo data first
	fLib_AC97_ReadData(base_address,RxBuf, 2);			// read first frame (no use frame)
	
	// real valid data
	fLib_AC97_ReadData(base_address,RxBuf, 1);			// read second frame - slot1(ECHO of Command address)
	if(((RxBuf[0] >> 12) & 0x7f) == Reg_Index)
	{
		fLib_AC97_ReadData(base_address,RxBuf, 1);		// read second frame - slot2 (status data port)
		*data = (RxBuf[0] >> 4) & 0x0000ffff;				
	}
			
	return true;
}

void fLib_AC97_WriteRegister(UINT32 base_address,UINT32 Reg_Index, UINT32 data)
{
	UINT32 TxBuf[3];

	TxBuf[0] = 0xE000;
	TxBuf[1] = 0x00000+(Reg_Index<<12);
	TxBuf[2] = data << 4;
	 
	fLib_SSPClearTxFIFO(base_address);
	fLib_SSPClearRxFIFO(base_address);	
	fLib_AC97_SetSlotValidReg(base_address,0x7);
		
	fLib_AC97_WriteData(base_address,TxBuf, 3);	
	fLib_SetSSP_Enable(base_address,true);
	while(((inw(base_address+SSP_STATUS)&SSP_TFVE)>>12)>0)
		;
		
	fLib_SetSSP_Enable(base_address,false);		
}

void fLib_AC97_WriteRegisterEx(UINT32 base_address,UINT32 Reg_Index, UINT32 data)
{
	UINT32 TxBuf[2];

	TxBuf[0] = 0x00000+(Reg_Index<<12);
	TxBuf[1] = data << 4;
	 
	fLib_SSPClearTxFIFO(base_address);
	fLib_SSPClearRxFIFO(base_address);	
	fLib_AC97_SetSlotValidReg(base_address,0xE000);	// slot0, slot1, slot2
		
	fLib_AC97_WriteData(base_address,TxBuf, 2);
	fLib_SetSSP_Enable(base_address,true);

	while(fLib_ReturnTxFIFO_Count(base_address)>0)
		;

	fLib_SetSSP_Enable(base_address,false);
}

void fLib_SetSSP_Reset(UINT32 base_address)
{
	outw(base_address + SSP_CONTROL2, SSP_RST);
}


void ssp_play_init(void)
{
    UINT32  clk_div;
    UINT32  tmp_i;

    UINT32 io_base = SSP_FTSSP010_0_PA_BASE;
    UINT32 mclk = 24576000;//12288000; 
    UINT32 sample_rate = 16000; 

    //Disable SSP
    fLib_SetSSP_Enable(io_base,0);

    //Clear FIFO
    fLib_SSPClearTxFIFO(io_base);
    fLib_SSPClearRxFIFO(io_base);

    //reset SSP
    fLib_SetSSP_Reset(io_base);
    fLib_I2S_Init(io_base, NORMAL_I2S);

    clk_div = mclk/(2*2*16*sample_rate)-1;
    fLib_SetSSPClkdiv(io_base, clk_div);

    //set DMA request threshold & enable DMA
    fLib_SetSSP_FIFO_Threshold(io_base, 0x10, 0x08);
    //fLib_SetSSP_DMA(io_base, 1, 1);

    //enable txen & rxen 
    tmp_i = inw(io_base + SSP_CONTROL2); 
    tmp_i |= 0x180;
    outw(io_base + SSP_CONTROL2, tmp_i);

    //Enable SSPEN & TXDOE
    fLib_SetSSP_Enable(io_base,1);
}

void SSP_RegPrint(void)
{
	u32 val;

    val = inw(SSP_FTSSP010_0_PA_BASE + SSP_CONTROL0);
    fLib_printf("SSP010 Reg 0x00 = 0x%08X.\r\n", val);

	val = inw(SSP_FTSSP010_0_PA_BASE + SSP_CONTROL1);
    fLib_printf("SSP010 Reg 0x04 = 0x%08X.\r\n", val);

	val = inw(SSP_FTSSP010_0_PA_BASE + SSP_CONTROL2);
    fLib_printf("SSP010 Reg 0x08 = 0x%08X.\r\n", val);

	val = inw(SSP_FTSSP010_0_PA_BASE + SSP_STATUS);
    fLib_printf("SSP010 Reg 0x0C = 0x%08X.\r\n", val);

	val = inw(SSP_FTSSP010_0_PA_BASE + SSP_INT_CONTROL);
    fLib_printf("SSP010 Reg 0x10 = 0x%08X.\r\n", val);

	val = inw(SSP_FTSSP010_0_PA_BASE + SSP_INT_STATUS);
    fLib_printf("SSP010 Reg 0x14 = 0x%08X.\r\n", val);

	val = inw(SSP_FTSSP010_0_PA_BASE + SSP_DATA);
    fLib_printf("SSP010 Reg 0x18 = 0x%08X.\r\n", val);

	val = inw(SSP_FTSSP010_0_PA_BASE + SSP_FEATURE);
    fLib_printf("SSP010 Reg 0x64 = 0x%08X.\r\n", val);
	return;
}


